import * as fs from "fs";
import * as hx from "hbuilderx";
import path = require("path");
import {CountInfo} from "./type";
import { hasExt, startsWithDot } from "./common"
import notifier from "./notifier";

export class Counter {
    readonly SingleAnnotationSign = "//";
    readonly MuiltAnnotationSignBegin = "/*";
    readonly MuiltAnnotationSignEnd = "*/";
    constructor(public readonly filePath: string) {
    }

    count(filePath: string | undefined = undefined): Promise<CountInfo[]> {
        hx.window.showToast({ message: "正在统计..." });

        return new Promise(async (resolve, reject) => {
            let fp: string = filePath || this.filePath;

            if (fs.statSync(fp).isFile()) {
                resolve([...validate([fp]).map(f => this.countFile(f))]);
                return;
            }
            else {
                resolve(validate(await this.getDirFiles(fp)).map(f => this.countFile(f)));
                return;
            }

            function validate(files: string[]): string[] {
                const config = hx.workspace.getConfiguration()
                const noSuffixCFG = config.get("count.file.lines.1.filters.non.suffix", true) as boolean;
                const startsWithDotCFG = config.get("count.file.lines.2.filters.dot.preffix", true) as boolean;
                const ignoreIncaseCFG = config.get("count.file.lines.3.filters.ignore.incase", true) as boolean;
                const ignoreCFG = (config.get("count.file.lines.4.filters.ignore", "") as string).trim();
                const matchIncaseCFG = config.get("count.file.lines.5.filters.match.incase", true) as boolean;
                const matchCFG = (config.get("count.file.lines.6.filters.match", "") as string).trim();

                const isDotPrefix = (filePath: string): boolean => {
                    return startsWithDotCFG && startsWithDot(filePath, true);
                }
                const filter = (filePath: string): boolean => {
                    if (ignoreCFG.trim() == "") {
                        return false;
                    }
                    return !![...new Set(ignoreCFG.split(";"))].filter(reg => !!reg).find(reg => filePath.match(new RegExp(reg, `g${ignoreIncaseCFG ? 'i' : ""}`)));
                }
                const matcher = (filePath: string): boolean => {
                    if (matchCFG.trim() == "") {
                        return true;
                    }

                    return !![...new Set(matchCFG.split(";"))].filter(reg => !!reg).find(reg => filePath.match(new RegExp(reg, `g${matchIncaseCFG ? 'i' : ""}`)))
                } 

                return files.filter(file => {
                    if (isDotPrefix(file)
                        || filter(file)
                        || !matcher(file)
                        || (noSuffixCFG && !hasExt(file))
                    ) {
                        return false;
                    }

                    return true;
                })
            }
        });
    }

    private countFile(filePath: string): CountInfo {
        notifier.notify(`正在统计${filePath}...`);
        const info: CountInfo = {
            filePath: filePath,
            count: 0,
            emptyCount: 0,
            annotationCount: 0
        };

        const data = fs.readFileSync(filePath).toString();
        const lines = data.split("\n");
        info.count = lines.length;

        let inBlockComment = false;

        for (let line of lines) {
            let trimmedLine = line.trim();

            // 空行
            if (!trimmedLine) {
                info.emptyCount++;
                continue;
            }

            // 纯注释行（单行注释，且没有其他代码）
            if (trimmedLine.startsWith(this.SingleAnnotationSign)) {
                info.annotationCount++;
                continue;
            }

            // 纯注释行（多行注释块，且整行都是注释）
            if (!inBlockComment && trimmedLine.startsWith(this.MuiltAnnotationSignBegin)) {
                // 检查是否本行闭合
                const beginIdx = trimmedLine.indexOf(this.MuiltAnnotationSignBegin);
                const endIdx = trimmedLine.indexOf(this.MuiltAnnotationSignEnd, beginIdx + 2);

                if (endIdx !== -1) {
                    // /* ... */ 在同一行，且前后都没有其他代码
                    const before = trimmedLine.substring(0, beginIdx).trim();
                    const after = trimmedLine.substring(endIdx + 2).trim();
                    if (!before && !after) {
                        info.annotationCount++;
                    }
                    // 不设置 inBlockComment
                } else {
                    // /* 开头，且之后没有代码为纯注释行，进入多行注释状态
                    const before = trimmedLine.substring(0, beginIdx).trim();
                    const after = trimmedLine.substring(beginIdx + 2).trim();
                    if (!before && !after) {
                        info.annotationCount++;
                    }
                    inBlockComment = true;
                }
                continue;
            }

            // 多行注释块内部
            if (inBlockComment) {
                // 检查是否结束
                const endIdx = trimmedLine.indexOf(this.MuiltAnnotationSignEnd);
                if (endIdx === -1) {
                    // 整行都在注释内
                    info.annotationCount++;
                } else {
                    // /* ... */ 结尾，检查结尾后是否还有代码
                    const after = trimmedLine.substring(endIdx + 2).trim();
                    const before = trimmedLine.substring(0, endIdx).trim();
                    if (!after && !before) {
                        // 只有 */ 的一行
                        info.annotationCount++;
                    }
                    inBlockComment = false;
                }
                continue;
            }
        }

        return info;
    }

    private getDirFiles(dirPath: string): Promise<string[]> {
        return new Promise((resolve, reject) => {
            fs.readdir(dirPath, async (err, files) => {
                if (err) {
                    resolve([]);
                }
                let filePaths: string[] = [];
                for (let item of files) {
                    let itemPath = path.join(dirPath, item);
                    if (!fs.existsSync(itemPath)) {
                        continue;
                    }
                    let stat = fs.statSync(itemPath);
                    if (stat.isDirectory()) {
                        filePaths.push(... await this.getDirFiles(itemPath));
                    }
                    else {
                        filePaths.push(itemPath);
                    }
                }

                resolve(filePaths);
            });
        });
    }
}